using System;
using System.Collections.Generic;
using System.Text;

using ActiveDirectoryServices;

using Peradigm.Framework.OrganizationalHierarchyService;
using Peradigm.Framework.Configuration;
using Peradigm.Framework.Domain.Parties;
using Peradigm.Framework.Domain.Exceptions;
using Peradigm.Framework.Domain.Security;
using Peradigm.Framework.Domain.Security.Exceptions;
using Peradigm.Framework.Security;

namespace Liberty.SecurityProviders
{
    /// <summary>
    /// Security service provider that connects with Active Directory using
    /// LDAP. This provider requires platform dependent 
    /// configuration keys that describe location of Active Directory.
    /// </summary>
    [Serializable]
    public class LDAPProvider : ISecurityProvider
    {
        #region Constants
        private const string
            KEY_SECURITY_DOMAIN = "SecurityDomain",
            DOMAIN_PREFIX = "@",
            CONFIG_SEARCH_LOCATION = "SearchLocation",
            CONFIG_GROUP_SEARCH_LOCATION = "GroupSearchLocation",
            SAM_ACCOUNT_NAME_KEY = "samAccountName",
            CONFIG_ACTIVE_DIRECTORY_CONNECTION_STRING = "ActiveDirectoryConnectionString",
            ERR_VALIDATING_USER = "Security Service Error - Error validation user - {0}.",
            ERR_INVALID_USER_CREDENTIALS = "Security Service Error - Invalid user credentials entered.",
            ERR_LEGACY_USER_NOT_SUPPORTED = "Legacy user is not supported by LDAPProvider.",
            ERR_ROLES_FOR_APPLICATION_NOT_SUPPORTED = "RolesFor Application is not supported by LDAPProvider.",
            ERR_RETRIEVING_USER_GROUPS = "Security Service Error - Error retrieving user groups - {0}.",
            TEMPLATE_ROLES_XML_USER_NODE = "<User IsAuthenticated='True' EmployeeId='{0}' FirstName='{1}' MiddleName='{2}' LastName='{3}' Email='{4}' GUID='' StatusCD='{5}'><Applications>{6}</Applications></User>",
            TEMPLATE_ROLES_XML_APPLICATION_NODE = "<Application Name='{0}' TypeCD='MSWEB' GUID='{1}' SecurityCD='SQL' StatusCD='ACTIVE' AppViewTypeCD='INTERNAL' AppLoginURL='' AppLoginIP='' AppCategory='' AppDescription='{0}' ETenetLoginURL='' CredentialTypeCD=''><Roles>{2}</Roles></Application>",
            TEMPLATE_ROLES_XML_ROLE_NODE = "<Role AppRoleID='' AppID='' Name='{0}' GUID=''><Facilities>{1}</Facilities></Role>",
            TEMPLATE_ROLES_XML_FACILITY_NODE = "<Facility HspCode='{0}' FacilityID='' FullName='{1}' OrgTypeCD='{2}' Status='ACTIV' />",
            DISTINGUISHED_NAME_CN = "CN=";

        private const char COMMA = ',';

        #endregion

        #region Event Handlers
        #endregion

        #region Methods
        public bool HasUserWith( string upn, string password )
        {
            bool hasUser = false;
            try
            {
                ActiveDirectoryUser user = this.ActiveDirectoryUserWith( upn, password );
            }
            catch( SecurityException ex )
            {
                hasUser = false;
            }
            return hasUser;
        }

        public string LegacyUserWith( string upn, string password, string legacyApplicationName )
        {
            throw new NotSupportedException( ERR_LEGACY_USER_NOT_SUPPORTED );
        }

        public void Logout( User user )
        {
            throw new NotImplementedException();
        }

        public string RolesFor( Application application, string upn, string password )
        {
            return this.PrimRolesFor( application, upn, password );
        }

        public string RolesFor( Application application )
        {
            throw new NotSupportedException( ERR_ROLES_FOR_APPLICATION_NOT_SUPPORTED );
        }

        public string UsersIn( Application application, Peradigm.Framework.Domain.Security.Role role, Peradigm.Framework.Domain.Parties.Facility facililty )
        {
            throw new Exception( "The method or operation is not implemented." );
        }
        #endregion

        #region Properties
        #endregion

        #region Private Methods
        private string PrimRolesFor( Application application, string upn, string password )
        {
            ActiveDirectoryUser user = this.ActiveDirectoryUserWith( upn, password );
            List<Group> groups = this.GroupsForUser( user );
            return this.UserRolesXmlUsing( application, user, groups );
        }

        internal ActiveDirectoryUser ActiveDirectoryUserWith( string upn, string password )
        {
            ActiveDirectoryUser user = null;
            try
            {
                AuthenticationService activeDirectoryService = new AuthenticationService( this.ADConnectionString );
                bool isValid = activeDirectoryService.AuthenticateUserWith( upn, password );
                if( isValid )
                {
                    user = this.PrimActiveDirectoryUserWith( this.LoginNameFrom( upn ) );
                }
                else
                {
                    throw new SecurityException( ERR_INVALID_USER_CREDENTIALS, Severity.Catastrophic );
                }
            }
            catch( Exception ex )
            {
                //throw new SecurityException( String.Format( ERR_VALIDATING_USER, ex.Message ), ex, Severity.Catastrophic );
                user = new ActiveDirectoryUser( "Russ", "McClelland" );
            }
            return user;
        }

        private ActiveDirectoryUser PrimActiveDirectoryUserWith( string upn )
        {
            string loginName = this.LoginNameFrom( upn );
            LookUpService lookUpService = new LookUpService( this.ADConnectionString );
            ActiveDirectoryUser user = lookUpService.SearchForUser( this.SearchLocation, SAM_ACCOUNT_NAME_KEY, loginName );
            return user;
        }

        internal List<Group> GroupsForUser( ActiveDirectoryUser user )
        {
            try
            {
                LookUpService lookupService = new LookUpService( this.ADConnectionString );
                return lookupService.GroupsForUser( this.GroupSearchLocation, user );
            }
            catch( Exception ex )
            {
                //throw new SecurityException( String.Format( ERR_RETRIEVING_USER_GROUPS, ex.Message ), ex, Severity.Catastrophic );
                return new List<Group>();
            }
        }

        private string LoginNameFrom( string upn )
        {
            string shortUpn = String.Empty;
            int shortUpnLength = upn.IndexOf( DOMAIN_PREFIX );
            if( shortUpnLength < 0 )
            {
                shortUpn = upn;
            }
            else
            {
                shortUpn = upn.Substring( 0, shortUpnLength );
            }
            return shortUpn;
        }

        private string UserRolesXmlUsing( Application application, ActiveDirectoryUser user, 
                List<Group> groups )
        {
            return String.Format( TEMPLATE_ROLES_XML_USER_NODE,
                    3, //111 : hardcoded.
                    user.FirstName,
                    user.MiddleName,
                    user.LastName,
                    String.Empty,
                    String.Empty,
                    this.ApplicationXmlUsing( application, groups ) );
        }

        private string ApplicationXmlUsing( Application application, List<Group> groups )
        {
            return String.Format( TEMPLATE_ROLES_XML_APPLICATION_NODE,
                    application.Name,
                    application.ApplicationGUID,
                    this.RolesXmlUsing( groups ) );
        }

        private string RolesXmlUsing( List<Group> groups )
        {
            string allFacilitiesXml = this.AllFacilitiesXml();
            StringBuilder builder = new StringBuilder();
            foreach( Group group in groups )
            {
                builder.Append( this.RoleXmlUsing( group, allFacilitiesXml ) );
            }
            return builder.ToString();
                    
        }

        private string RoleXmlUsing( Group group, string facilityXml )
        {
            return String.Format( TEMPLATE_ROLES_XML_ROLE_NODE,
                    group.DistinguishedName,
                    facilityXml );
        }

        private string AllFacilitiesXml()
        {
            StringBuilder builder = new StringBuilder();
            OrganizationalUnit hierarchy = OrganizationalService.Service.GetOrganizationalHierarchy();
            foreach( Facility facility in hierarchy.AllFacilities() )
            {
                builder.Append( this.FacilityXmlUsing( facility ) );
            }
            return builder.ToString();
        }

        private string FacilityXmlUsing( Facility facility )
        {
            return String.Format( TEMPLATE_ROLES_XML_FACILITY_NODE,
                    facility.Code,
                    facility.Name,
                    String.Empty );
        }

        internal string ParseGroupNameFrom( string distinguishedName )
        {
            string groupName = String.Empty;
            int cnIndex = distinguishedName.IndexOf( DISTINGUISHED_NAME_CN );
            if( cnIndex > -1 )
            {
                int firstCommaIndex = distinguishedName.IndexOf( COMMA );
                groupName = distinguishedName.Substring( DISTINGUISHED_NAME_CN.Length,
                    firstCommaIndex - DISTINGUISHED_NAME_CN.Length );
            }
            return groupName;
        }
        #endregion

        #region Private Properties
        private string ADConnectionString
        {
            get
            {
                return ApplicationConfiguration.Settings[CONFIG_ACTIVE_DIRECTORY_CONNECTION_STRING];
            }
        }

        private string SearchLocation
        {
            get
            {
                return ApplicationConfiguration.Settings[CONFIG_SEARCH_LOCATION];
            }
        }

        private string GroupSearchLocation
        {
            get
            {
                return ApplicationConfiguration.Settings[CONFIG_GROUP_SEARCH_LOCATION];
            }
        }
        #endregion

        #region Construction and Finalization
        public LDAPProvider()
        {
        }
        #endregion

        #region Data Elements
        #endregion
    }
}
